﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;

namespace Axe.Data.Mapping
{
    public abstract class SqlAction
    {
        public delegate void PreambleDelegate(SqlCommand cmd);
        public delegate T QueryActionDelegate<T>(SqlCommand cmd);

        public SqlConnection Connection { get; set; }
        public SqlTransaction Transaction { get; set; }

        public bool RollbackTransactionOnError { get; set; }
        public bool CommitTransactionOnSuccess { get; set; }
        public bool CloseConnection { get; set; }

        public SqlAction(SqlConnection conn)
        {
            Connection = conn;
            CloseConnection = false;
            Transaction = null;
            RollbackTransactionOnError = false;
            CommitTransactionOnSuccess = false;
        }

        public SqlAction(SqlConnection conn,bool close)
        {
            Connection = conn;
            CloseConnection = close;
            Transaction = null;
            RollbackTransactionOnError = false;
            CommitTransactionOnSuccess = false;
        }

        public SqlAction(SqlConnection conn, bool close, SqlTransaction tran, bool rollback, bool commit)
        {
            Connection = conn;
            CloseConnection = close;
            Transaction = tran;
            RollbackTransactionOnError = rollback;
            CommitTransactionOnSuccess = commit;
        }

        #region protected internal string RegisterCondition(SqlCommand cmd, Condition c, MappingDescription descr)
        /// <summary>
        /// Регистрирует параметр условия в объекте <paramref name="cmd"/> и возвращает подстроку SQL запроса.
        /// </summary>
        /// <param name="cmd"></param>
        /// <param name="c">Условие</param>
        /// <param name="descr">Метаописание объекта-сущности</param>
        /// <returns>Подстрока запроса, соответствующая данному условию</returns>
        internal string RegisterCondition(SqlCommand cmd, Condition c, MappingDescription descr)
        {
            string mappedName = descr.MappedFields[c.PropertyName].MappedName;
            string paramName = String.Format("@{0}_{1}", mappedName, GetHashCode());
            string sqlOp = SqlClauseBuilder.GetOperator(c.Operator);

            string query = String.Format("{0} {1} {2}", mappedName, sqlOp, paramName);
            cmd.Parameters.AddWithValue(paramName, c.Value);
            return query;
        } 
        #endregion

        #region protected string RegisterCondition(SqlCommand cmd, Condition c, MappingDescription descr)
        /// <summary>
        /// Регистрирует параметр условия в объекте <paramref name="cmd"/> и возвращает подстроку SQL запроса.
        /// </summary>
        /// <param name="cmd"></param>
        /// <param name="c">Условие</param>
        /// <param name="descr">Метаописание объекта-сущности</param>
        /// <returns>Подстрока запроса, соответствующая данному условию</returns>
        internal string RegisterCondition(SqlCommand cmd, CondAggregation c, MappingDescription descr)
        {
            string leftCondition = null;
            string rightCondition = null;

            if(c.Left is CondAggregation)
                leftCondition = RegisterCondition(cmd, c.Left as CondAggregation, descr);
            else if(c.Left is Condition)
                leftCondition = RegisterCondition(cmd, c.Left as Condition, descr);
            else 
                throw new InvalidOperationException("Unsupported condition");

            if(c.Right is CondAggregation)
                rightCondition = RegisterCondition(cmd, c.Right as CondAggregation, descr);
            else if(c.Left is Condition)
                rightCondition = RegisterCondition(cmd, c.Right as Condition, descr);
            else 
                throw new InvalidOperationException("Unsupported condition");

            switch (c.Aggregation)
            {
                case ConditionAggregation.And:
                    return String.Format("({0}) AND ({1})", leftCondition, rightCondition);

                case ConditionAggregation.Or:
                    return String.Format("({0}) OR ({1})", leftCondition, rightCondition);
                default:
                    throw new InvalidOperationException("Unsupported ConditionAggregation");
            }
        }
        #endregion

        protected T Template<T>(PreambleDelegate preamble, QueryActionDelegate<T> action)
        {
            SqlCommand cmd = Connection.CreateCommand();
            preamble(cmd);

            try
            {
                if (Connection.State == System.Data.ConnectionState.Closed)
                    Connection.Open();

                T result = action(cmd);

                if (CommitTransactionOnSuccess && Transaction != null)
                    Transaction.Commit();
                return result;
            }
            catch (Exception e)
            {
                if (RollbackTransactionOnError && Transaction != null)
                    Transaction.Rollback();

                throw e;
            }
            finally
            {
                if (CloseConnection && Connection != null)
                    Connection.Close();
            }
        }
    }
}
